package kii.wapis
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	
	import kii.events.FaultEvent;
	import kii.events.ResultEvent;
	
	import mx.managers.CursorManager;
	import mx.rpc.mxml.IMXMLSupport;
	
	[Event(type="kii.events.ResultEvent", name="result")]
	[Event(type="kii.events.FaultEvent", name="fault")]
	[Event(type="kii.events.ServiceEvent", name="invoke")]
	
	[DefaultProperty("children")]
	/**
	 * <code>Service<code>
	 * @author David "Nirth" Sergey
	 */
	public class Service extends EventDispatcher
						 implements IService, IMXMLSupport
	{
		public function Service(endPoint:String = null)
		{
			super();
			this.endPoint = endPoint;
		}
		/**
		 * @private
		 * Method invoked when endPoint is defined, implement in subclasses.
		 */
		protected function initialize():void
		{		
		}
		/**
		 * @protected
		 */
		protected var _sharedParams:Vector.<Param>;
		[Bindable]
		/**
		 * Collection of shared across all operations and children.
		 */
		public function get sharedParams():Vector.<Param>
		{
			if(!_sharedParams)
				_sharedParams = new Vector.<Param>();
			if(parent && parent.sharedParams)
				return parent.sharedParams.concat(_sharedParams);
			return _sharedParams;
		}
		public function set sharedParams(value:Vector.<Param>):void
		{
			_sharedParams = value;
		}
		/**
		 * @private
		 */
		protected var _children:Vector.<IService>;
		/**
		 * Vector of IService instances that are children to current item. 
		 */
		public function get children():Vector.<IService>
		{
			if(!_children)
				_children = new Vector.<IService>();
			return _children;
		}
		public function set children(value:Vector.<IService>):void
		{
			updateChildren(false)
			_children = value;
			updateChildren(true);
		}
		
		protected var _parent:IService;
		/**
		 * Indicates current service parent, if set to <code>null</code> if you want service to behave as root.
		 */
		[Bindable]
		public function get parent():IService
		{
			return _parent;
		}
		public function set parent(value:IService):void
		{
			if(_parent)
				_parent.remove(this);
			_parent = value;
			if(_parent && !_parent.contains(this))
				_parent.add(this);
		}
		/**
		 * @private
		 */
		protected var _apiKey:String;
		[Bindable]
		/**
		 * Service API Key if exists.
		 */
		public function get apiKey():String
		{
			if(parent && parent.apiKey)
				return parent.apiKey;
			return _apiKey;
		}
		public function set apiKey(value:String):void
		{
			_apiKey = value;
		}
		/**
		 * @private
		 */
		private var _endPoint:String;
		[Bindable]
		/**
		 * <code>IService</code> endPoint url.
		 */
		public function get endPoint():String
		{
			if(parent)
				return parent.endPoint + _endPoint;
			return _endPoint;
		}
		public function set endPoint(value:String):void
		{
			if(value)
			{
				if(value.charAt(value.length - 1) != '/')
					value += '/';
			}
			_endPoint = value;
			
			if(endPoint)
				initialize();
		}
		/**
		 * @private
		 */
		private var _resultFormat:String;
		[Bindable]
		
		public function get resultFormat():String
		{
			return _resultFormat;
		}
		public function set resultFormat(value:String):void
		{
			_resultFormat = value;
			if(value != ResultFormat.CUSTOM)
				parser = null;
		}
		
		private var _lastResult:Object;
		[Bindable("result")]
		public function get lastResult():Object
		{
			return _lastResult;
		}
		
		private var _busy:Boolean = false;
		[Bindable("invoke")]
		[Bindable("fault")]
		[Bindable("result")]
		public function get busy():Boolean
		{
			return _busy;
		}
		/**
		 * @private
		 */
		private var _concurrency:String;
		[Bindable]
		/**
		 * @inheritDoc
		 */
		public function get concurrency():String
		{
			return _concurrency;
		}
		public function set concurrency(value:String):void
		{
			_concurrency = value;
		}
		/**
		 * @private
		 */
		private var _showBusyCursor:Boolean;
		/**
		 * @inheritDoc
		 */
		[Bindable]
		public function get showBusyCursor():Boolean
		{
			return _showBusyCursor;
		}
		public function set showBusyCursor(value:Boolean):void
		{
			_showBusyCursor = value;
			
			if(value && busy)
				CursorManager.setBusyCursor();
			if(!value)
				CursorManager.removeBusyCursor();
		}
		/**
		 * @private
		 */
		private var _parser:IParse;
		[Bindable]
		/**
		 * Sets default parser for the current service implementation.
		 */		
		public function get parser():IParse
		{
			return _parser;
		}
		public function set parser(value:IParse):void
		{
			_parser = value;
			if(value)
				resultFormat = ResultFormat.CUSTOM;
		}
		/**
		 * Adds new <code>IService</code> instance to children list, child service will have it's endPoint modified.
		 * @param child  <code>IService</code> instance to add.
		 * @return       Added <code>IService</code> instance.
		 */
		public function add(child:IService):IService
		{
			_children.push(child);
			if(child.parent != this)
				child.parent = this;
			return child;
		}
		/**
		 * Removes <code>IService</code> instance from children list.
		 * @param child  Child to remove.
		 * @return       Removed child.
		 */
		public function remove(child:IService):IService
		{
			_children.splice(_children.indexOf(child), 1);
			if(child.parent == this)
				child.parent = null;
			return child;
		}
		/**
		 * Checks whether child is in children list or not.
		 * @param child  Child to check.
		 * @return       <code>true</code> if contains, otherwise false.
		 */
		public function contains(child:IService):Boolean
		{
			return _children.indexOf(child) == -1 ? false : true;
		}
		/**
		 * Invokes Operation using GET method. 
		 * @param url		URL extension of the operation, for example 'myOperation.jsp'.
		 * @param params	Object containing parameters to send.
		 * @return 			<code>AsyncToken</code> instance for this unique operation.
		 */		
		public function get(url:String, params:Object = null):AsyncToken
		{
			return send(url, params, URLRequestMethod.GET);
		}
		/**
		 * Invokes Operation using POST method. 
		 * @param url		Full or partial urls allowed, for example 'http://site.com/service/action' or 'service/action', you have to define <code>endPoint</code> in order to be able to send partial urls.
		 * @param params	Hash of parameters to send.
		 * @return 			<code>AsyncToken</code> instance for this unique operation.
		 */		
		public function post(url:String, params:Object = null):AsyncToken
		{
			return send(url, params, URLRequestMethod.POST);
		}
		
		/**
		 * @private 
		 * @param url		Full or partial urls allowed, for example 'http://site.com/service/action' or 'service/action', you have to define <code>endPoint</code> in order to be able to send partial urls.
		 * @param params	Hash of parameters to send.
		 * @param method	URL method.
		 * @return 			<code>AsyncToken</code> instance for this unique operation.
		 */
		protected function send(url:String, params:Object, method:String = 'GET'):AsyncToken
		{
			busyCursor(true);
			var data:URLVariables = new URLVariables;
			if ( sharedParams )
				for each ( var param:Param in sharedParams)
					data[param.name] = this[param.property];
			for ( var property:String in params )
				data[property] = params[property];

			var request:URLRequest = new URLRequest();
				request.url = url.indexOf('http') ? endPoint + url : url;
				request.method = method;
				request.data = data;
			var operation:Operation = new Operation(this);
				operation.load(request);
			log('Service:send', url);
			return new AsyncToken(operation);
		}
		/**
		 * @private
		 */
		internal function resultCallback(event:Event):ResultEvent
		{
			busyCursor(false);
			var resultEvent:ResultEvent = new ResultEvent(ResultEvent.RESULT,
														  proccessResult(event.target.data),
														  event.target.url);
			dispatchEvent(resultEvent);
			handleResult(resultEvent);
			log('Service:result', event.target.url);
			return resultEvent;		
		}
		/**
		 * @private
		 */
		internal function faultCallback(event:Event):FaultEvent
		{
			busyCursor(false);
			if(event is IOErrorEvent)
				var faultString:String = (event as IOErrorEvent).text;
			else if(event is SecurityErrorEvent)
				faultString = (event as SecurityErrorEvent).text;
			else
				faultString = 'fault';
			var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT, faultString);
			dispatchEvent(faultEvent);
			handleFault ( faultEvent );
			log('Service:fault', event.target.url);
			return faultEvent;
		}
		/**
		 * @private
		 * Default handler for ResultEvent, implement in subclasses.
		 */
		protected function handleResult(event:ResultEvent):void
		{
			log( 'Service:handleResult' );
		}
		/**
		 * @private
		 * Default handler for FaultEvent, implement in subclasses.
		 */
		protected function handleFault(event:FaultEvent):void
		{
			log ( 'Service:handleFault' );
		}
		/**
		 * @private
		 * Checks for busy cursor.
		 */
		protected function busyCursor(busy:Boolean):void
		{
			_busy = busy;
			if(showBusyCursor && busy)
				CursorManager.setBusyCursor();
			else
				CursorManager.removeBusyCursor();
		}
		/**
		 * @private
		 */
		protected function updateChildren(add:Boolean = true):void
		{
			for each ( var child:IService in _children)
				if ( add )
					child.parent = this;
				else
					child.parent = null;
		}
		/**
		 * @private
		 */
		protected function proccessResult(data:Object):Object
		{
			if(resultFormat == ResultFormat.CUSTOM)
				_lastResult = _parser.result(data);
			else if(resultFormat == ResultFormat.XML)
				_lastResult = XML(data);
			else
				_lastResult = data.toString();
			
			_lastResult = data;
			
			return _lastResult;
		}
	}
}